/* ----------------------------------------------------------------------
 *	Shadow Quest
 *	Copyright (C) 2009 Prageeth Silva <prageeth@thenewcoders.com>
 *
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *  ----------------------------------------------------------------------
 */
package map;

import java.util.ArrayList;

import org.newdawn.slick.Animation;
import org.newdawn.slick.Color;
import org.newdawn.slick.Graphics;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;

import sq.FontLoader;
import sq.ImageLoader;
import sq.LoadScript;
import sq.game.Quest;
import units.Unit;
import units.Unit.AttackType;

/** Items that can be picked, dropped and used by units
 * 
 */
public class Item extends MapEntity {

    /**
     *
     */
    private static final long serialVersionUID = 1L;
    protected static String RESOURCE_PATH = "assets/items/";
    protected static String SCRIPT_CREATE = "item.lua";
    protected static String SCRIPT_NAME = "script.lua";
    private static final String FUNC_CREATE = "create";
    private static final String FUNC_INIT = "init";
    private static final String FUNC_USE = "use";
    private static final String FUNC_DESTROY = "destroy";
    // constant values (loaded from an XML file)
    public static int AMULET_HP = 40,
            SWORD_DAMAGE = 10,
            TOME_COOLDOWN = -200,
            BOW_RANGE_DAMAGE = 14,
            POTION_HEAL = 20,
            ARMOR_DEFENCE = 2;
    public static double RING_HEALRATE = 2.5f, CLOAK_SPEED = 0.5f;
    public static int DEFAULT_WIDTH = 60, DEFAULT_HEIGHT = 60;

    // the type of the item
    public enum ItemType {

        AMULET, SWORD, TOME, ELIXIR, BOW, COINS, EXPLOSIVE, POTION, ARMOR, RING, CLOAK
    }
    private Animation use, destroyed;
    private Image image, inventory, mark;			// the image used to display
    private int id;									// the unique id
    private double healRate, heal, hp;
    private boolean picked;					// status of the item, if its picked up or not
    private boolean inUse, usable;
    private boolean isDest, destroyable;
    private int damage, rangeDamage, cool, defence;	// hit-points / health, damage, cool down rate
    private ItemType type;							// item type
    private AttackType attackType;				// property to change the attack type of the unit carrying the item
    private int value, timer;						// value used for coins
    private ArrayList<Item> duplicates;
    private int explodingDamage, explodingTime, explodingRange;
    private LoadScript script;
    private String sPath;
    private static int count = 0;

    /** Create a new Item object .
     * @param type The type of item.
     * @param x The x coordinate of the item (in pixels).
     * @param y The y coordinate of the item (in pixels).
     * @param value The value of the item.
     */
    public Item(World world, String itemName, String displayName, int value)
            throws SlickException {
        super(world);

        this.setId(count++);
        // TODO : add the load script
        this.createFromScript();

        this.setName(displayName);
        this.setValue(value);
        this.setDestroyed(false);

        // TODO i:setAttackType(Unit.AttackType.NONE)  
        this.setAttackType(AttackType.NONE);
        this.setDuplicates(new ArrayList<Item>());

        this.setDirectoryPath(Item.RESOURCE_PATH + itemName + "/");
        String scriptPath = this.getDirectoryPath() + Item.SCRIPT_NAME;
        this.activateScript(scriptPath);
        this.initialiseFromScript();
        this.finalizeLoading();



//    	switch (type) {
//    		case AMULET:
//    			imagePath = "amulet.png";
//    			this.setName("Amulet of Vitality");
//    	    	this.setHP(AMULET_HP);
//    			break;
//     		case SWORD:
//        		imagePath = "sword.png";
//        		this.setName("Sword of Strength");
//            	this.setDamage(SWORD_DAMAGE);
//    			break;
//     		case TOME:
//    			imagePath = "tome.png";
//    			this.setName("Tome of Agility");
//    	    	this.setCoolDownRate(TOME_COOLDOWN);
//    			break;
//     		case POTION:
//    			imagePath = "potion.png";
//    			this.setName("Potion of Health");
//    	    	this.setHeal(POTION_HEAL);
//    	    	this.setUsable(true);
//    			break; 
//     		case ARMOR:
//    			imagePath = "armor.png";
//    			this.setName("Armor");
//    			this.setDefence(ARMOR_DEFENCE);
//    			break; 
//     		case RING:
//    			imagePath = "ring.png";
//    			this.setName("Ring of Healing");
//    	    	this.setHealRate(RING_HEALRATE);
//    			break; 
//     		case CLOAK:
//    			imagePath = "cloak.png";
//    			this.setName("Cloak of Speed");
//    	    	this.setSpeed(CLOAK_SPEED);
//    			break; 
//      		case ELIXIR:
//            	imagePath = "elixir.png";
//            	this.setName("Elixir of Life");
//    			break;
//      		case BOW:
//            	imagePath = "bow.png";
//            	this.setName("Bow of Range");
//            	this.setRangeDamage(BOW_RANGE_DAMAGE);
//            	this.setAttackType(AttackType.ARCHER);
//            	this.setUsable(true);
//    			break;
//      		case COINS:
//            	imagePath = "coins.png";
//    			break;
//      		case EXPLOSIVE:
//      			imagePath = "explosive.png";
//      			String usePath = "u_", destPath = "d_";
//            	this.setName("Explosive");
//            	this.setRangeDamage(EXPLODING_DAMAGE);
//            	this.setTimer(EXPLODING_TIME);
//            	this.setUsable(true);
//            	this.setInUseAnimation(new Image(dirPath+usePath+imagePath));
//            	this.setDestroyedAnimation(new Image(dirPath+destPath+imagePath));
////            	this.setMarkImage(new Image(dirPath+markPath+imagePath));
//    			break;
//    	}
//    	this.setImage(new Image(dirPath+imagePath));
//    	this.setInventoryImage(new Image(dirPath+inventoryPath+imagePath));

    }

    public final void finalizeLoading()
            throws SlickException {
        this.loadImages(this.getDirectoryPath());
    }

    /** Loads all the common images for this unit.
     * @param dirPath The path of the directory where the images are in (including the following "/").
     * @throws SlickException 
     */
    private void loadImages(String dirPath)
            throws SlickException {

        this.setImage(ImageLoader.getLoadedImage(dirPath + "item.png"));
        this.setInventoryImage(ImageLoader.getLoadedImage(dirPath + "inventory.png"));

    }

    /** Render the the item at the given position on the map.
     * @param g The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @param cameraOffsetY The number of pixels the world has been offset to in the x-direction (pixels).
     */
    public void renderOnMap(Graphics g, double cameraOffsetX, double cameraOffsetY) {

        boolean exp = (this.getItemType() == ItemType.EXPLOSIVE);

        if (exp && this.isInUse()) {
            //this.renderActiveRange(g, cameraOffsetX, cameraOffsetY);
            double width = this.getInUseAnimation().getCurrentFrame().getWidth();
            double height = this.getInUseAnimation().getCurrentFrame().getHeight();
            this.getInUseAnimation().draw((float) (this.getX() - cameraOffsetX - width / 2), (float) (this.getY() - cameraOffsetY - height / 2));
        } else if (exp && this.isDestroyed()) {
            double width = this.getDestroyedAnimation().getCurrentFrame().getWidth();
            double height = this.getDestroyedAnimation().getCurrentFrame().getHeight();
            this.getDestroyedAnimation().draw((float) (this.getX() - cameraOffsetX - width / 2), (float) (this.getY() - cameraOffsetY - height / 2));
        } else {
            this.getImage().draw((float) (this.getX() - cameraOffsetX - this.getWidth() / 2), (float) (this.getY() - cameraOffsetY - this.getHeight() / 2));
        }

    }

    /** Render the item at the given coordinates on the status panel.
     * @param g The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @param cameraOffsetY The number of pixels the world has been offset to in the x-direction (pixels).
     */
    public void renderOnPanel(Graphics g, double offsetX, double offsetY) {
        // draw the item on the panel
        this.getInventoryImage().draw((float) offsetX, (float) offsetY);
        // if the item is of type COIN, then draw the value at the top right corner.
        if (this.getValue() > 1) {
            g.setColor(Color.red);
            g.setFont(FontLoader.getFontSize10());
            String str = String.valueOf(this.getValue());
            double offset = 3f, width = g.getFont().getWidth(str);
            double x = offsetX + this.getWidth() - width - 2 * offset;
            double y = offsetY + offset;
            g.drawString(str, (float)x, (float)y);
        }

    }

    /** Render the item's (e.g.: explosive) active area 
     * @param g The Slick graphics object, used for drawing.
     * @param cameraOffsetX The number of pixels the world has been offset to in the x-direction (pixels).
     * @param cameraOffsetY The number of pixels the world has been offset to in the x-direction (pixels).
     */
    public void renderActiveRange(Graphics g, double cameraOffsetX, double cameraOffsetY) {
        double width = this.getExplodingRange() * 2;
        double height = width;
        g.setColor(FontLoader.getDangerColor());
        g.fillOval((float) (this.getX() - cameraOffsetX - width / 2), (float) (this.getY() - cameraOffsetY - height / 2), (float) width, (float) height);
    }

    /** Make the item get picked up from the map.
     * 
     */
    public void getPickedUp() {

        this.setOnMap(false);

        // TODO add sound

    }

    /** Checks if the given Item is exactly the same as this Item.
     * @param item The item to be compared.
     * @return true if the items are the same, else false.
     */
    public boolean equals(Item item) {
        return this.getId() == item.getId();
    }

    /** Checks if the given Item is exactly the same as this Item.
     * @param item The item to be compared.
     * @return true if the items are the same, else false.
     */
    public boolean typeEquals(Item item) {
        return this.getItemType() == item.getItemType();
    }

    /* Static Methods */
    /** Search an item in a units inventory.
     * @param inventory The unit to be search in.
     * @param id The unique ID of the item.
     * @return The item found, or null if the item wasn't found.
     */
    public static Item searchItem(ArrayList<Item> inventory, int id) {
        for (Item i : inventory) {
            if (i.getId() == id) {
                return i;
            }
        }
        return null;
    }

    /** Search an item in a units inventory.
     * @param unit The unit to be search in.
     * @param itemType The item describes as item type.
     * @return The item found, or null if the item wasn't found.
     */
    public static Item searchItem(Unit unit, ItemType itemType) {
        for (Item i : unit.getInventory()) {
            if (i.getItemType() == itemType) {
                return i;
            }
        }
        return null;
    }

    /** Search an item in a units inventory.
     * @param unit The unit to be search in.
     * @param item The item.
     * @return The item found, or null if the item wasn't found.
     */
    public static Item searchItem(Unit unit, Item item) {
        return searchItem(unit, item.getItemType());
    }

    /** Reset the items count (used for ID).
     */
    public static void resetCount() {
        count = 0;
    }

    /* Getters and Setters beyond this point. */
    /** Get the Height of the item.
     * @return The height.
     */
    @Override
    public double getHeight() {
        return (this.isOnMap() ? this.getImage().getHeight() : this.getInventoryImage().getHeight());
    }

    /** Get the Width of the item.
     * @return The width.
     */
    @Override
    public double getWidth() {
        return (this.isOnMap() ? this.getImage().getWidth() : this.getInventoryImage().getWidth());
    }

    /** Get the display image of the item.
     * @return The Display Image.
     */
    public Image getImage() {
        return this.image;
    }

    /** Set the display image of the item.
     * @param image The Display Image.
     */
    public void setImage(Image image) {
        this.image = image;
    }

    /** Get the display image of the item.
     * @return The Display Image.
     */
    public Image getMarkImage() {
        return this.mark;
    }

    /** Set the display image of the item.
     * @param image The Display Image.
     */
    public void setMarkImage(Image image) {
        this.mark = image;
    }

    /** Get the display image of the item.
     * @return The Display Image.
     */
    public Image getInventoryImage() {
        return this.inventory;
    }

    /** Set the display image of the item.
     * @param image The Display Image.
     */
    public void setInventoryImage(Image image) {
        this.inventory = image;
    }

    /** Get the display name of the item.
     * @return The Display Name.
     */
    @Override
    public String getName() {
        if (this.getItemType() == ItemType.COINS) {
            return (this.getValue() + " Gold Coin" + (this.getValue() > 1 ? "s" : ""));
        }
        return super.getName();
    }

    /** Checks if the item has been picked up.
     * @return False is the item is picked by the player, else true.
     */
    public boolean isPickedUp() {
        return this.picked;
    }

    /** Sets if the item has been picked up.
     * @param picked The status if the item is picked or not.
     */
    public void setPickedUpStatus(boolean picked) {
        this.picked = picked;
    }

    /** Get the health of the item.
     * @return The Health value.
     */
    public double getHP() {
        return this.hp;
    }

    /** Set the health of the item.
     * @param health The Health Value.
     */
    public void setHP(double hp) {
        this.hp = hp;
    }

    /** Get the damage of the item.
     * @return The damage.
     */
    public int getDamage() {
        return this.damage;
    }

    /** Set the damage of the item.
     * @param damage The damage.
     */
    public void setDamage(int damage) {
        this.damage = damage;
    }

    /** Get the range damage of the item.
     * @return The damage.
     */
    public int getRangeDamage() {
        return this.rangeDamage;
    }

    /** Set the range damage of the item.
     * @param damage The damage.
     */
    public void setRangeDamage(int damage) {
        this.rangeDamage = (damage < 0) ? 0 : damage;
    }

    /** Get the cool down rate of the item.
     * @return The cool down rate.
     */
    public int getCoolDownRate() {
        return this.cool;
    }

    /** Set the cool down rate of the item.
     * @param rate The cool down rate.
     */
    public void setCoolDownRate(int rate) {
        this.cool = rate;
    }

    /** Get the item type
     * @return The item type.
     */
    public ItemType getItemType() {
        return this.type;
    }

    /** Set the item type.
     * @param type The item type.
     */
    public void setItemType(ItemType type) {
        this.type = type;
    }

    /** Get the attack type of the item.
     * @return The attack type.
     */
    public AttackType getAttackType() {
        return this.attackType;
    }

    /** Set the attack type of the item.
     * @param type The attack type.
     */
    public final void setAttackType(AttackType type) {
        this.attackType = type;
    }

    public int getValue() {
        return this.value;
    }

    public final void setValue(int value) {
        this.value = value;
    }

    public int getId() {
        return this.id;
    }

    private void setId(int id) {
        this.id = id;
    }

    private LoadScript getScript() {
        return this.script;
    }

    private void setScript(final String fileName) {
        this.script = new LoadScript(fileName);
    }

    public boolean isUsable() {
        return this.usable;
    }

    public void setUsable(boolean usable) {
        this.usable = usable;
    }

    public boolean isInUse() {
        return this.inUse;
    }

    public void setInUse(boolean inUse) {
        this.inUse = inUse;
    }

    /** Use the item
     * @param unit The unit that uses the item.
     */
    public void use(Unit unit) {

        this.getWorld().getQuest().addMsgToStatus(unit.getName() + " is using " + this.getName() + ".", true);
        this.setInUse(this.isUsable());
        this.useFromScript(unit);

//		if (this.getItemType() == ItemType.EXPLOSIVE) 
//		{
//			this.setInUse(this.isUsable());
//			this.getInUseAnimation().restart();
//		}
//		else if (this.getItemType() == ItemType.POTION) 
//		{
//			this.setInUse(this.isUsable());
//			double hp = unit.getHP()+this.getHeal();
//			unit.setHP((hp>=unit.getMaxHP()?unit.getMaxHP():hp));
//		}

    }

    protected void stopUsing() {
        this.setInUse(false);
    }

    /** Update the item for a frame
     * @param world The reference to the world in which the Item exists in.
     * @param delta The time elapsed (in milliseconds)
     */
    public void update(World world, int delta) {
        if (this.getItemType() == ItemType.EXPLOSIVE) {
            if (this.isInUse()) {
                if (this.getInUseAnimation().isStopped()) {
                    this.explode(world);
                    this.stopUsing();
                }
            } else if (this.isDestroyed()) {
                if (this.getDestroyedAnimation().isStopped()) {
                    this.setOnMap(false);
                    this.setDestroyed(false);
                }
            }
        } else if (this.getItemType() == ItemType.POTION) {
            if (this.isInUse()) {
                this.setOnMap(false);
            }
        }
    }

    public int getTimer() {
        return this.timer;
    }

    public void setTimer(int timer) {
        this.timer = timer;
    }

    /** Make the item explode (Explosive)
     * @param world The reference to the world in which the item exists
     */
    private void explode(World world) {

        this.setDestroyed(true);
        this.getDestroyedAnimation().restart();

        for (OtherObject o : world.getObjects()) {
            if (o.isExplodable()) {
                double dist2 = Unit.calculateDistanceSqrd(this.getX(), this.getY(), o.getX(), o.getY());
                if (dist2 <= Unit.Squared(this.getExplodingRange())) {
                    o.destroy();
                }
            }
        }

        world.attackUnitsOnGround(this.getX(), this.getY(), this.getExplodingRange(), this.getExplodingDamage(), this.getExplodingTime() / 10);

    }

    private Animation getInUseAnimation() {
        return this.use;
    }

    protected void setInUseAnimation(Image image) {
        SpriteSheet s = new SpriteSheet(image, 72, 72);
        int time = this.getExplodingTime() / s.getHorizontalCount();
        this.use = new Animation(s, time);
        this.use.setLooping(false);
    }

    private Animation getDestroyedAnimation() {
        return this.destroyed;
    }

    protected void setDestroyedAnimation(Image image) {
        this.destroyed = new Animation(new SpriteSheet(image, 162, 162), 100);
        this.destroyed.setLooping(false);
    }

    public boolean isDestroyed() {
        return this.isDest;
    }

    public final void setDestroyed(boolean dest) {
        this.isDest = dest;
    }

    public boolean isDestroyable() {
        return this.destroyable;
    }

    public void setDestroyable(boolean destroyable) {
        this.destroyable = destroyable;
    }

    public void destroy() {
        this.destroyFromScript();
    }

    public ArrayList<Item> getDuplicates() {
        return duplicates;
    }

    private void setDuplicates(ArrayList<Item> duplicates) {
        this.duplicates = duplicates;
    }

    public double getHealRate() {
        return this.healRate;
    }

    public void setHealRate(double healRate) {
        this.healRate = healRate;
    }

    public double getHeal() {
        return this.heal;
    }

    public void setHeal(double heal) {
        this.heal = heal;
    }

    public int getDefence() {
        return this.defence;
    }

    public void setDefence(int defence) {
        this.defence = defence;
    }

    private void createFromScript() {
        this.setScript(Item.RESOURCE_PATH + Item.SCRIPT_CREATE);
        this.getScript().runScriptFunction(Item.FUNC_CREATE, this);
        this.getScript().closeScript();
    }

    protected void activateScript(String dirPath, String itemName) {
        this.setScript(dirPath + itemName + ".lua");
    }

    protected final void activateScript(String fileName) {
        this.setScript(fileName);
    }

    protected final void initialiseFromScript() {
        this.getScript().runScriptFunction(Item.FUNC_INIT, this);
    }

    protected void destroyFromScript() {
        this.getScript().runScriptFunction(Item.FUNC_DESTROY, this);
    }

    protected void useFromScript(Unit unit) {
        ArrayList<MapEntity> objs = new ArrayList<MapEntity>(2);
        objs.add(this);
        objs.add(unit);
        this.getScript().runScriptFunction(Item.FUNC_USE, objs);
    }

    protected final String getDirectoryPath() {
        return this.sPath;
    }

    public final void setDirectoryPath(String path) {
        sPath = path;
    }

    public int getExplodingDamage() {
        return this.explodingDamage;
    }

    public void setExplodingDamage(int explodingDamage) {
        this.explodingDamage = explodingDamage;
    }

    public int getExplodingTime() {
        return this.explodingTime;
    }

    public void setExplodingTime(int explodingTime) {
        this.explodingTime = explodingTime;
    }

    public int getExplodingRange() {
        return this.explodingRange;
    }

    public void setExplodingRange(int explodingRange) {
        this.explodingRange = explodingRange;
    }
}
